package com.xlizen;

import net.wimpi.modbus.Modbus;
import net.wimpi.modbus.ModbusException;
import net.wimpi.modbus.ModbusIOException;
import net.wimpi.modbus.ModbusSlaveException;
import net.wimpi.modbus.io.ModbusTransaction;
import net.wimpi.modbus.io.ModbusTransport;
import net.wimpi.modbus.msg.ExceptionResponse;
import net.wimpi.modbus.msg.ModbusRequest;
import net.wimpi.modbus.msg.ModbusResponse;
import net.wimpi.modbus.util.AtomicCounter;
import net.wimpi.modbus.util.Mutex;

public class ModbusBleTransaction implements ModbusTransaction {

    //class attributes
    private static AtomicCounter c_TransactionID =
            new AtomicCounter(Modbus.DEFAULT_TRANSACTION_ID);

    //instance attributes and associations
    private ModbusTransport m_IO;
    private ModbusRequest m_Request;
    private ModbusResponse m_Response;
    private boolean m_ValidityCheck =
            Modbus.DEFAULT_VALIDITYCHECK;
    private int m_Retries = Modbus.DEFAULT_RETRIES;
    private int m_TransDelayMS = Modbus.DEFAULT_TRANSMIT_DELAY;
    private ModbusBleConnection m_BleCon;

    private Mutex m_TransactionLock = new Mutex();

    public ModbusBleTransaction() {
    }

    public ModbusBleTransaction(ModbusBleConnection con) {
        setBleCon(con);
    }

    public void setBleCon(ModbusBleConnection con) {
        this.m_BleCon = con;
        m_IO = con.getModbusTransport();
    }

    @Override
    public void setRequest(ModbusRequest modbusRequest) {
        m_Request = modbusRequest;
    }

    @Override
    public ModbusRequest getRequest() {
        return m_Request;
    }

    @Override
    public ModbusResponse getResponse() {
        return m_Response;
    }

    @Override
    public int getTransactionID() {
        return c_TransactionID.get();
    }

    @Override
    public void setRetries(int i) {
        m_Retries = i;
    }

    @Override
    public int getRetries() {
        return m_Retries;
    }

    @Override
    public void setCheckingValidity(boolean b) {
        m_ValidityCheck = b;
    }

    @Override
    public boolean isCheckingValidity() {
        return m_ValidityCheck;
    }

    @Override
    public void execute() throws ModbusException {
        assertExecutable();
        try {
            m_TransactionLock.acquire();
            synchronized (m_IO) {
                int tries = 0;
                boolean finished = false;
                m_Request.setTransactionID(c_TransactionID.increment());

                do {
                    try {
                        if (m_TransDelayMS > 0) {
                            try {
                                Thread.sleep(m_TransDelayMS);
                            } catch (InterruptedException ex) {
                                System.err.println("InterruptedException: " + ex.getMessage());
                            }
                        }
                        m_IO.writeMessage(m_Request);
                        m_Response = m_IO.readResponse();
                        finished = true;
                    } catch (ModbusIOException e) {
                        if (++tries >= m_Retries) {
                            throw e;
                        }
                        System.err.println("execute try " + tries + " error: " +
                                e.getMessage());
                    }
                } while (!finished);
            }
            if (m_Response instanceof ExceptionResponse) {
                throw new ModbusSlaveException(
                        ((ExceptionResponse) m_Response).getExceptionCode()
                );
            }
        } catch (InterruptedException ex) {
            throw new ModbusIOException("Thread acquiring lock was interrupted.");
        } finally {
            m_TransactionLock.release();
        }
    }

    /**
     * Asserts if this <tt>ModbusTCPTransaction</tt> is
     * executable.
     *
     * @throws ModbusException if the transaction cannot be asserted.
     */
    private void assertExecutable()
            throws ModbusException {
        if (m_Request == null ||
                m_BleCon == null) {
            throw new ModbusException(
                    "Assertion failed, transaction not executable"
            );
        }
    }//assertExecuteable
}
